#include "BaseAudio.h"

static const float DIST_FACTOR = 0.5f;

//-------------------------------------------------------------------------------------
BaseAudio::BaseAudio(void)
{
	alutInit(0, 0);
	mfVolume = 0.0f;
	mfListenerXPos = 0.0f;
	mfListenerYPos = 0.0f;
	mfListenerZPos = 0.0f;
	mBGMusic = 0;
}
//-------------------------------------------------------------------------------------
BaseAudio::~BaseAudio(void)
{
	// Release all sounds in OpenAL
	for (SourceMap::iterator it = mSourceMap.begin(); it != mSourceMap.end(); ++it)
	{
		// Stop playing
		alSourceStop((*it).first);
	
		// Remove from OpenAL
		alDeleteSources(1, &((*it).first));
		alDeleteBuffers(1, &((*it).second));
	}
	mSourceMap.clear();

	// Free cached sound files
	for (SoundMap::iterator it = mSoundMap.begin(); it != mSoundMap.end(); ++it)
	{
		// why?
		//free((*it).second.data);
	}

	alutExit();
}
//-------------------------------------------------------------------------------------
const bool BaseAudio::init(void)
{
	bool ret = true;

	if (!loadSoundFile("media/sounds/effects/activation.wav", SOUND_ACTIVATION)) ret = false;
	if (!loadSoundFile("media/sounds/effects/bigShot.wav", SOUND_BIGSHOT)) ret = false;
	if (!loadSoundFile("media/sounds/effects/death.wav", SOUND_DEATH)) ret = false;
	if (!loadSoundFile("media/sounds/effects/desintegrate.wav", SOUND_DESINTEGRATE)) ret = false;
	if (!loadSoundFile("media/sounds/effects/gainreward.wav", SOUND_GAINREWARD)) ret = false;
	if (!loadSoundFile("media/sounds/effects/hit1.wav", SOUND_HIT1)) ret = false;
	if (!loadSoundFile("media/sounds/effects/hit2.wav", SOUND_HIT2)) ret = false;
	if (!loadSoundFile("media/sounds/effects/Jump.wav", SOUND_JUMP)) ret = false;
	if (!loadSoundFile("media/sounds/effects/levelup.wav", SOUND_LEVELUP)) ret = false;
	if (!loadSoundFile("media/sounds/effects/menu.wav", SOUND_MENU)) ret = false;
	if (!loadSoundFile("media/sounds/effects/move.wav", SOUND_MOVE)) ret = false;
	if (!loadSoundFile("media/sounds/effects/playermelee.wav", SOUND_PLAYERMELEE)) ret = false;
	if (!loadSoundFile("media/sounds/effects/playermelee2.wav", SOUND_PLAYERMELEE2)) ret = false;
	if (!loadSoundFile("media/sounds/effects/playerranged.wav", SOUND_PLAYERRANGED)) ret = false;
	if (!loadSoundFile("media/sounds/effects/playerranged2.wav", SOUND_PLAYERRANGED2)) ret = false;
	if (!loadSoundFile("media/sounds/music/nervousBG.wav", SOUND_NERVOUSBG)) ret = false;
	if (!loadSoundFile("media/sounds/music/background1.wav", SOUND_BG1)) ret = false;
	if (!loadSoundFile("media/sounds/effects/altlaser.wav", SOUND_LASER)) ret = false;
	if (!loadSoundFile("media/sounds/effects/humm.wav", SOUND_HUMM)) ret = false;
	if (!loadSoundFile("media/sounds/effects/humm2.wav", SOUND_HUMM2)) ret = false;
	if (!loadSoundFile("media/sounds/effects/light.wav", SOUND_LIGHT)) ret = false;

	return ret;
}
//-------------------------------------------------------------------------------------
void BaseAudio::setListenerPosition(const ALfloat x, const ALfloat y, const ALfloat z)
{
	mfListenerXPos = x;
	mfListenerYPos = y;
	mfListenerZPos = z;
}
//-------------------------------------------------------------------------------------
void BaseAudio::setPosition(const ALuint source, const ALfloat x, const ALfloat y, const ALfloat z)
{
	alSource3f(source, AL_POSITION, DIST_FACTOR * (x - mfListenerXPos), DIST_FACTOR * (y - mfListenerYPos), DIST_FACTOR * (z - mfListenerZPos));
}
//-------------------------------------------------------------------------------------
void BaseAudio::setGlobalVolume(const ALfloat volume)
{
	mfVolume = volume;
	if (mfVolume < 0.0f) mfVolume = 0.0f;
	if (mfVolume > 1.0f) mfVolume = 1.0f;

	// Update sound volume of all sounds in OpenAL
	for (SourceMap::iterator it = mSourceMap.begin(); it != mSourceMap.end(); ++it)
	{
		alSourcef((*it).first, AL_PITCH, mfVolume);
	}
}
//-------------------------------------------------------------------------------------
const ALfloat BaseAudio::getGlobalVolume(void) const
{
	return mfVolume;
}
//-------------------------------------------------------------------------------------
const bool BaseAudio::isPlaying(const ALuint source)
{
	ALenum state;

	alGetSourcei(source, AL_SOURCE_STATE, &state);

	return (state == AL_PLAYING);
}
//-------------------------------------------------------------------------------------
const ALuint BaseAudio::playSound(const SoundType type, const bool loop)
{
	// Check if this sound type is cached
	SoundMap::iterator it = mSoundMap.find(type);
	if (it == mSoundMap.end()) return 0;

	ALuint source;
	ALuint buffer;
	SoundResource res = (*it).second;

	// Generate a source and a buffer in OpenAL
	alGenSources(1, &source);
	alGenBuffers(1, &buffer);

	// Set parameters of the sound
	alSourcef(source, AL_PITCH, mfVolume);
	alSourcef(source, AL_GAIN, 1);
	alSource3f(source, AL_POSITION, mfListenerXPos, mfListenerYPos, mfListenerZPos);
	alSource3f(source, AL_VELOCITY, 0, 0, 0);
	alSourcei(source, AL_LOOPING, loop);

	// Load sound in OpenAL
	alBufferData(buffer, res.format, res.data, res.size, ALsizei(res.freq));

	// Mark which buffer belongs to which source
	alSourcei(source, AL_BUFFER, buffer);

	// Check for errors
	if (alGetError() != AL_NO_ERROR) return 0;

	// Add sound to the map with all sounds in OpenAL
	mSourceMap[source] = buffer;

	playSound(source);

	return source;
}
//-------------------------------------------------------------------------------------
void BaseAudio::playSound(const ALuint source)
{
	if (!isPlaying(source)) alSourcePlay(source);
}
//-------------------------------------------------------------------------------------
void BaseAudio::stopSound(const ALuint source) const
{
	alSourceStop(source);
}
//-------------------------------------------------------------------------------------
void BaseAudio::pauseSound(const ALuint source) const
{
	alSourcePause(source);
}
//-------------------------------------------------------------------------------------
void BaseAudio::releaseSound(const ALuint source)
{
	// Check if the sound was already released
	SourceMap::iterator it = mSourceMap.find(source);
	if (it == mSourceMap.end()) return;

	// Stop playing
	alSourceStop(source);
	
	// Remove from OpenAL
	alDeleteSources(1, &source);
	alDeleteBuffers(1, &((*it).second));

	// Remove from the map with all sounds in OpenAL
	mSourceMap.erase(it);
}
//-------------------------------------------------------------------------------------
const bool BaseAudio::loadSoundFile(const char *filename, const SoundType type)
{
	// Check if this sound type is already loaded
	if (mSoundMap.find(type) != mSoundMap.end()) return false;

	// Load and cache sound file
	SoundResource res;
	res.data = alutLoadMemoryFromFile(filename, &res.format, &res.size, &res.freq);

	// Check for errors
	if (alutGetError() != ALUT_ERROR_NO_ERROR) return false;

	mSoundMap[type] = res;

	return true;
}
//-------------------------------------------------------------------------------------
void BaseAudio::update(void)
{
	ALint loop;

	// Remove sound form OpenAL if it's ready with playing and is not a loop sound
	SourceMap::reverse_iterator it_end = mSourceMap.rend();
	for (SourceMap::reverse_iterator it = mSourceMap.rbegin(); it != it_end; ++it)
	{
		alGetSourcei((*it).first, AL_LOOPING, &loop);
		if (!loop && !isPlaying((*it).first))
		{
			releaseSound((*it).first);
		}
	}
}
//-------------------------------------------------------------------------------------
void BaseAudio::setBGMusic(const SoundType type)
{
	mBGMusic = playSound(type, true);
}
//-------------------------------------------------------------------------------------
void BaseAudio::stopBGMusic(void)
{
	if (mBGMusic) stopSound(mBGMusic);
}